package xj.graph2d.tools;

import static xj.graph2d.DrawShapeConstants.HIGH_MASK;
import static xj.graph2d.DrawShapeConstants.LOW_MASK;
import static xj.graph2d.DrawShapeConstants.PointOnHorizontalDivider;
import static xj.graph2d.DrawShapeConstants.PointOnPolyShapeEdge;
import static xj.graph2d.DrawShapeConstants.PointOnPolyShapeVertex;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Map;
import java.util.Observer;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import xj.app.DrawTool;
import xj.graph2d.Document;
import xj.graph2d.DrawCanvas;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeFactory;
import xj.graph2d.GraphEdgeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.SelfLoop;
import xj.graph2d.SimpleDrawDocument;
import xj.graph2d.SimpleDrawView;
import xj.graph2d.app.DrawApp;
import xj.graph2d.app.ToolPanel;
import xj.graph2d.app.prop.UMLAssociationModelPane;
import xj.graph2d.shapes.BarShape;
import xj.graph2d.shapes.GraphEdgeShape;
import xj.graph2d.shapes.HorizontalCompartments;
import xj.graph2d.shapes.PolyShape;
import xj.graph2d.shapes.RectangularShape;
import xj.graph2d.shapes.ShapeConstants;
import xj.graph2d.shapes.ShapeIcon;
import xj.graph2d.shapes.TitledShape;
import xj.graph2d.shapes.block.BlockShape;
import xj.graph2d.shapes.block.StatementShape;
import xj.graph2d.shapes.uml.ClassShape;
import xj.graph2d.shapes.uml.StateShape;
import xj.graph2d.template.Template;
import xj.graph2d.template.TemplateToolButton;
import xj.meta.ModelElement;
import xj.model.uml.State;
import xj.model.uml.UMLAssociation;
import xj.model.uml.UMLClass;
import xj.util.gui.JPanelPopup;


//////////////////////////////////////////////////////////////////////////
///
/// Handling popup triggered by mouse hover over a shape 
///
//////////////////////////////////////////////////////////////////////////

public class HoverPopupManager { 

  // sides of a node
  public static final int TOP_SIDE     = 0x0;
  public static final int BOTTOM_SIDE  = 0x1;
  public static final int LEFT_SIDE    = 0x2;
  public static final int RIGHT_SIDE   = 0x3;

  // ends of an edge
  public static final int LEFT_END  = 0x0;
  public static final int RIGHT_END = 0x1;

  public static final int BOUNDARY    = 0x0;
  public static final int INTERIOR    = 0x10000;
  public static final int COMPARTMENT = 0x15000;
  public static final int CURRENT     = 0x20000;

  static final Color ADD_BUTTON_COLOR    = new Color(0, 205, 0, 128);  
  static final Color REMOVE_BUTTON_COLOR = new Color(205, 0, 0, 128);  

  protected DrawApp main;
  protected SimpleDrawView view;
  protected SimpleDrawDocument doc;
  protected UMLPointTool umltool; 
  protected Template currentTemplate = null;

  protected JButton[] shapeActions = null;
  protected JButton[][] nodeActions = null;
  protected JComponent[] edgeActions = null; 

  protected JButton[] subnodeActions = null;

  protected JComponent[][] gapActions = null;

  // node/edge template name -> action 
  protected static Map<String, JButton[]>    shapeActionsMap = new HashMap<String, JButton[]>();
  protected static Map<String, JButton[][]>  nodeActionsMap  = new HashMap<String, JButton[][]>();
  protected static Map<String, JComponent[]> edgeActionsMap  = new HashMap<String, JComponent[]>();

  protected static Map<String, JButton[]>    subnodeActionsMap  = new HashMap<String, JButton[]>();

  // template name -> action 
  protected static Map<String, JComponent[][]> gapActionsMap = new HashMap<String, JComponent[][]>();

  PopupMouseAction popupMouseAction = new PopupMouseAction();

  UMLClassPopupAction classShapeAction = new UMLClassPopupAction();    
  UMLAssociationModelPane assocModelPane = null; 

  // actions and buttons available in all diagrams 
  PolyShapeAction polyShapeAction = new PolyShapeAction();
  CompartmentAction compartmentAction = new CompartmentAction();

  JButton[] polyShapeButton = new JButton[2];
  JButton[] compartmentButton = new JButton[2];

  public HoverPopupManager(DrawApp main) { 
    this.main = main;
    //String templateName = "UMLClassDiagram";
    //Template template = main.getTemplate(templateName);

    // initialize action buttons available in all diagrams 
    int i;
    for (i = 0; i <= 1; i++) { 
      polyShapeButton[i] = new JButton();
      polyShapeButton[i].setBorder(null);
      polyShapeButton[i].addActionListener(polyShapeAction);
      if (i == 0) { 
        polyShapeButton[i].setIcon(new TextIcon("-", REMOVE_BUTTON_COLOR)); 
        polyShapeButton[i].setActionCommand("remove");
      } else { 
        polyShapeButton[i].setIcon(new TextIcon("+", ADD_BUTTON_COLOR));
        polyShapeButton[i].setActionCommand("add");
      }
    }
    for (i = 0; i <= 1; i++) { 
      compartmentButton[i] = new JButton();
      compartmentButton[i].setBorder(null);
      compartmentButton[i].addActionListener(compartmentAction);
      if (i == 0) { 
        compartmentButton[i].setIcon(new TextIcon("-", REMOVE_BUTTON_COLOR)); 
        compartmentButton[i].setActionCommand("remove");
      } else { 
        compartmentButton[i].setIcon(new TextIcon("+", ADD_BUTTON_COLOR)); 
        compartmentButton[i].setActionCommand("add");
      }
    }

  }

  // called from UMLTool.start()
  public void setTool(UMLPointTool umltool) { 
    this.umltool = umltool;
    view = umltool.getView(); 
    doc = view.getSimpleDrawDocument();
    Template temp = null;
    if (main != null) { 
      temp = main.getCurrentTemplate();
      if (temp != null) { 	  
	if (temp != currentTemplate) { 
	  currentTemplate = temp;
	  loadGapActions();
	  //gapPopup = null;
	}
      } else {
	currentTemplate = null;
      }
    } else { 
      currentTemplate = null;
    }
    if (temp == null) { 
      shapeActions = null;
      nodeActions = null;
      edgeActions = null;
      subnodeActions = null;
      gapActions = null;
      //gapPopup = null;
    }
  }

  void loadGapActions() { 
    if (currentTemplate != null) { 
      String templateName = currentTemplate.getName();
      gapActions = gapActionsMap.get(templateName);
      if (gapActions == null) {
	gapActions = makeGapActions();
	if (gapActions != null) { 
	  gapActionsMap.put(templateName, gapActions);
	}
      }
    }
  }

  JComponent[][] makeGapActions() { 
    if (currentTemplate != null) { 
      String[][] gapToolNames = currentTemplate.getGapToolNames();
      if (gapToolNames != null) { 
	JButton[][] gapToolButtons = new JButton[gapToolNames.length][];
	ActionListener actionListener = new NodeInsertionAction();
	for (int i = 0; i < gapToolNames.length; i++) { 
	  gapToolButtons[i] = new JButton[gapToolNames[i].length];
	  for (int j = 0; j < gapToolNames[i].length; j++) {	    
	    TemplateToolButton tb = currentTemplate.getButton(gapToolNames[i][j]);
	    String command = currentTemplate.getName() + "." + tb.name; 
	    DrawShape iconShape = tb.iconShape;
	    if (iconShape == null) { 
	      iconShape = tb.shape;
	    }
	    Icon icon = new ShapeIcon(iconShape, tb.width, tb.height);
	    JButton  button = new JButton(icon);
	    button.setActionCommand(command);
	    button.addActionListener(actionListener);
	    button.addMouseListener(popupMouseAction);
	    gapToolButtons[i][j] = button;
	  }
	}
	return gapToolButtons;
      }
    }
    return null;
  }

  void loadNodeActions(String nodeTempName) {
    //System.out.println("HoverPopupManager.loadNodeActions() " + nodeTempName); 

    if (nodeTempName != null) { 
      nodeActions = nodeActionsMap.get(nodeTempName);
      shapeActions = shapeActionsMap.get(nodeTempName);
      if (nodeActions == null) { 
	makeNodeActions(nodeTempName);
      }
    } else { 
      nodeActions = null;
      shapeActions = null;
    }
  }

  void loadSubnodeActions(String nodeTempName) {
    //System.out.println("HoverPopupManager.loadSubnodeActions() " + nodeTempName); 

    if (nodeTempName != null) { 
      subnodeActions = subnodeActionsMap.get(nodeTempName);
      if (subnodeActions == null) { 
	makeSubnodeActions(nodeTempName);
      }
    } else { 
      subnodeActions = null;
    }
  }

  void makeNodeActions(String nodeTempName) { 
    if (nodeTempName != null) { 
      int i;
      i = nodeTempName.indexOf('.');
      String templateName = nodeTempName.substring(0, i);
      Template template = main.getTemplate(templateName);
      String[] shapeToolNames = Template.getShapeToolNames(nodeTempName);
      String[][] nodeToolNames = Template.getNodeToolNames(nodeTempName);

      if (debug) { 
	System.out.println("HoverPopupManager.makeNodeActions() shapeToolNames " + 
			   (shapeToolNames == null ? "null" : shapeToolNames.length));
	System.out.println("HoverPopupManager.makeNodeActions() nodeToolNames " + 
			   (nodeToolNames == null ? "null" : nodeToolNames.length));
      }

      if (shapeToolNames != null) { 
	JButton[] nodeShapeButtons = new JButton[shapeToolNames.length];
	for (i = 0; i < shapeToolNames.length; i++) { 
	  Icon icon = template.getIcon(shapeToolNames[i]);
	  JButton  button = new JButton(icon);
	  if (i == 0) { 
	    button.setActionCommand("hide"); 
	  } else { 
	    button.setActionCommand("show"); 
	  }
	  //button.setMargin(new Insets(2, 2, 2, 2));
	  //button.setBackground(Color.white);
	  button.addActionListener(classShapeAction);
	  button.addMouseListener(popupMouseAction);
	  nodeShapeButtons[i] = button;
	}
	shapeActionsMap.put(nodeTempName, nodeShapeButtons);
	shapeActions = nodeShapeButtons;
      }

      if (nodeToolNames != null) { 
	JButton[][] nodeToolButtons = new JButton[4][]; 
	for (i = 0; i < nodeToolNames.length; i++) { 
	  int len = nodeToolNames[i].length;
	  nodeToolButtons[i] = new JButton[len];
	  for (int j = 0; j < nodeToolNames[i].length; j++) { 
	    String toolName = nodeToolNames[i][j];
	    if (toolName != null) { 
	      TemplateToolButton tb = template.getButton(toolName);
	      String command = template.getName() + "." + tb.name; 
	      Icon icon = new ShapeIcon(tb.iconShape, tb.width, tb.height);
	      DrawShape shape = tb.shape;
	      DrawShapeFactory factory = DrawShapeFactory.getInstance();
	      if (shape != null) { 
		shape.setTemplateName(command);
		factory.addPrototype(command, shape);
		DrawApp.addIcon(command, icon);
	      } else if (tb.protoName != null) {
		shape = factory.getPrototype(template.getName() + "." + tb.protoName);  
	      }
	      SimpleDrawTool tool0 = ToolPanel.makeShapeTool(shape, tb.reverseEnds, command, main);
	      SimpleDrawTool tool = new NodeEdgeTool(tool0);
	      main.addTool(command, tool);
	      
	      JButton  button = new JButton(icon);
	      button.setActionCommand(command);
	      NodeEdgeAction action = new NodeEdgeAction();
	      action.setSide(i); 
	      //button.setMargin(new Insets(2, 2, 2, 2));
	      //button.setBackground(Color.white);
	      button.addActionListener(action);
	      button.addMouseListener(popupMouseAction);
	      nodeToolButtons[i][j] = button;
	    }
	  }
	}
	nodeActionsMap.put(nodeTempName, nodeToolButtons);
	nodeActions = nodeToolButtons;
      }
    }
  }

  void makeSubnodeActions(String nodeTempName) { 
    if (nodeTempName != null) { 
      int i;
      i = nodeTempName.indexOf('.');
      String templateName = nodeTempName.substring(0, i);
      Template template = main.getTemplate(templateName);
      String[] subnodeToolNames = Template.getSubnodeToolNames(nodeTempName);

      if (subnodeToolNames != null) { 	
	JButton[] subnodeToolButtons = new JButton[subnodeToolNames.length]; 
	ActionListener actionListener = new SubnodeInsertionAction();
	for (i = 0; i < subnodeToolNames.length; i++) { 
	  String toolName = subnodeToolNames[i];
	  if (toolName != null) { 
	    TemplateToolButton tb = template.getButton(toolName);
	    String command = template.getName() + "." + tb.name; 
	    DrawShape iconShape = tb.iconShape;
	    if (iconShape == null) { 
	      iconShape = tb.shape;
	    }
	    Icon icon = new ShapeIcon(iconShape, tb.width, tb.height);
	    JButton  button = new JButton(icon);
	    button.setActionCommand(command);
	    button.addActionListener(actionListener);
	    button.addMouseListener(popupMouseAction);
	    subnodeToolButtons[i] = button;	    
	  }
	}
	subnodeActionsMap.put(nodeTempName, subnodeToolButtons);
	subnodeActions = subnodeToolButtons;
      }
    }
  }

  // return position of popup: TOP_SIDE BOTTOM_SIDE LEFT_SIDE RIGHT_SIDE CURRENT
  // return -1, if no popup 
  protected int makeHoverPopup(DrawShape overShape, 
			       int pos, Point p, Point2D cur) {
    if (overShape != null && 
	p != null && 
	cur != null) {       

      if (debug) { 
	System.out.println("HoverPopupManager.makeHoverPopup() tempNmae= " + overShape.getTemplateName());
      }

      if (doc.getDocumentType() == Document.DocumentType.Visual_Class_Diagram) { 
	return makeVisualClassHoverPopup(overShape, pos, p, cur);	
      }

      ModelElement model = overShape.getModel();

      if (debug) { 
        System.out.println("HoverPopupManager model= " + model);
      }
      
      if (model != null) { 
        if (overShape instanceof GraphNodeBase) { 	
	  String tempName = overShape.getTemplateName(); 
	  loadNodeActions(tempName);    

	  if (debug) { 
	    System.out.println("HoverPopupManager shapeActions " + 
			       (shapeActions == null ? "null" : shapeActions.length));
	    System.out.println("HoverPopupManager nodeActions " + 
			       (nodeActions == null ? "null" : nodeActions.length));
	  }

          int ppos = getClosestSide(overShape, cur); 
          if (popup.isSame(overShape, ppos)) { 
            return ppos;
          } else {
            if (model instanceof UMLClass && 
		nodeActions != null) { 
	      return makeUMLClassPopup(overShape, ppos, p);
            } else if (model instanceof State) { 
	      State state = (State) model;
	      if (debug) { 
		System.out.println("HoverPopupManager state " + state.getType());
	      }

	      if (overShape instanceof StateShape) { 
		StateShape stshape = (StateShape) overShape;  
		return makeUMLStatePopup(state, stshape, ppos, pos, p, cur);
	      } else { 
		if (debug) { 
		  System.out.println("HoverPopupManager state: not StateShape");
		}
		return makeGraphNodePopup(overShape, ppos, p);
		
	      }
	    } else if (nodeActions != null) {
	      // default for graph node
	      return makeGraphNodePopup(overShape, ppos, p);
            }
          }

        } else if (overShape instanceof GraphEdgeBase) { 
          if (debug) { 
            System.out.println("HoverPopupManager edge");
          }
          GraphEdgeBase edge = (GraphEdgeBase) overShape; 	  
          float px = (float) cur.getX();
          float py = (float) cur.getY();	      
          boolean end1 = edge.isNearEnd1(px, py);
          boolean end2 = edge.isNearEnd2(px, py);
          int ppos = -1;
          if (end1 || end2) { 
            ppos = CURRENT + (end1 ? 0 : 1); 	
          }    

          if (popup.isSame(overShape, ppos)) { 
            popup.setPopupLocation(p);
            return ppos;
          } else { 
            if (model instanceof UMLAssociation) {
	      UMLAssociation assoc = (UMLAssociation) model; 
	      int ap = makeUMLAssociationPopup(assoc, edge, end1, end2, ppos, p);
	      if (ap != -1) { 
		return ap; 
	      }
            }	      
          }	    
        }
      } // model != null 

      if (debug) {
	System.out.println("HoverPopupManager: @1 " + overShape.getClass().getName());
      }

      boolean ispolyshape = false; 
      if (overShape instanceof GraphEdgeShape) { 
	GraphEdgeShape eshape = (GraphEdgeShape) overShape; 
	GraphEdgeBase edge = eshape.getEdgeShape();
	ispolyshape = (edge instanceof PolyShape && 
		       !(edge instanceof SelfLoop));
      } else { 
	ispolyshape = (overShape instanceof PolyShape && 
		       !(overShape instanceof SelfLoop));
      }
      if (ispolyshape) { 
        if (debug) { 
          System.out.println("HoverPopupManager: PolyShape");
        }

	PolyShape polyshape = (PolyShape) overShape;
	return makePolyShapePopup(polyshape, pos, p);
      } 

    }
    return -1;
  }

  protected int makeVisualClassHoverPopup(DrawShape overShape, 
					  int pos, Point p, Point2D cur) {
    //System.out.println("HoverPopupManager.makeVisualClassHoverPopup()");

    if (overShape != null && 
	p != null && 
	cur != null) {       
      if (overShape instanceof BlockShape) {
	String tempName = overShape.getTemplateName(); 
	loadSubnodeActions(tempName);    

	if (subnodeActions != null && 
	    subnodeActions.length > 0) { 
	  for (int i = 0; i < subnodeActions.length; i++) { 
	    JButton  button = subnodeActions[i];
	    ActionListener[] a = button.getActionListeners(); 
	    if (a != null) { 
	      for (int j = 0; j < a.length; j++) { 
		if (a[j] instanceof SubnodeInsertionAction) { 
		  SubnodeInsertionAction action = (SubnodeInsertionAction) a[j];
		  action.setParent((GraphNodeBase) overShape);
		}
	      }
	    }
	  }
	  popup.hidePopup();
	  popup.setShape(overShape, CURRENT);
	  popup.setPopupLocation(p);
	  popup.setComponents(subnodeActions, true);	    
	  return CURRENT;
	}
      } else if (overShape instanceof BarShape) { 
	
      } else if (overShape instanceof StatementShape) {
	     
      }
    }
    return -1; 
  }

  protected int makeUMLClassPopup(DrawShape overShape, int ppos, Point p) { 
    if (overShape != null) { 
      //int i;
      if (ppos == TOP_SIDE) { 
	if (overShape instanceof ClassShape) { 
	  ClassShape cshape = (ClassShape) overShape;
	  int n = cshape.getNumberOfHorizontalCompartments();
	  JButton button; 
	  if (n == 1) {
	    button = shapeActions[1];
	  } else { 
	    button = shapeActions[0];
	  }       
	  nodeActions[ppos][0] = button; 
	  classShapeAction.shape = cshape;
	}
      }
      return makeGraphNodePopup(overShape, ppos, p);
    }
    return -1;
  }

  protected int makeUMLStatePopup(State state, StateShape stshape, 
				  int ppos, int pos, 
				  Point p, Point2D cur) { 
    if (state != null && 
	stshape != null) { 
      int pos1 = pos & HIGH_MASK;
      int pos2 = pos & LOW_MASK;

      if (state.getType() == State.StateType.Orthogonal_State) {
	if (pos1 == PointOnHorizontalDivider) { 
	  if (debug) { 
	    System.out.println("HoverPopupManager state: action: " + stshape.isActionCompartmentShown() + 
			       "   pos2: " + pos2);
	  }
	  
	  if (pos2 >= 1 && 
	      (!stshape.isActionCompartmentShown() ||  pos2 >= 2)) { 
	    ppos = INTERIOR + pos2; 
	    if (popup.isSame(stshape, ppos)) { 
	      //if (!popup.isShown()) { 
	      popup.setPopupLocation(p);
	      return ppos;
	      //}
	    } else { 
	      if (debug) { 
		System.out.println("HoverPopupManager state PointOnHorizontalDivider");
	      }
	      compartmentAction.hc = (HorizontalCompartments) stshape;
	      compartmentAction.position = pos; 		 
	      //popupVisible = false;
	      popup.hidePopup();
	      popup.setShape(stshape, ppos);
	      popup.setPopupLocation(p);
	      popup.setComponents(compartmentButton, false);
	      return ppos;
	    }
	  }
	} else { 
	  if (nearBoundary(stshape, cur)) {
	    return makeGraphNodePopup(stshape, ppos, p);
	  } else { 		      
	    // interior, display compartment add button 
	    if (debug) { 
	      System.out.println("HoverPopupManager state: interior compartment: pos2: " + pos2);
	    }
	    
	    ppos = COMPARTMENT + pos2; 
	    if (popup.isSame(stshape, ppos)) { 
	      popup.setPopupLocation(p);
	      return ppos;
	    } else { 
	      compartmentAction.hc = (HorizontalCompartments) stshape;
	      compartmentAction.position = pos; 		 
	      popup.hidePopup();
	      popup.setShape(stshape, ppos);
	      popup.setPopupLocation(p);
	      popup.setComponent(compartmentButton[1]);
	      return ppos;
	    }
	  }
	}
      } else { 
	// not orthogonal state  
	if (debug) { 
	  System.out.println("HoverPopupManager state: not orthogonal state");
	}
	return makeGraphNodePopup(stshape, ppos, p);
      }
    }
    return -1;
  }

  protected int makeGraphNodePopup(DrawShape overShape, int ppos, Point p) { 
    if (nodeActions != null && 
	nodeActions[ppos] != null && 
	nodeActions[ppos].length > 0) {
      boolean vertical = (ppos == LEFT_SIDE || ppos == RIGHT_SIDE);
      for (int i = 0; i < nodeActions[ppos].length; i++) { 
	JButton  button = nodeActions[ppos][i];
	ActionListener[] a = button.getActionListeners(); 
	if (a != null) { 
	  for (int j = 0; j < a.length; j++) { 
	    if (a[j] instanceof NodeEdgeAction) { 
	      NodeEdgeAction action = (NodeEdgeAction) a[j];
	      action.setNode((GraphNodeBase) overShape);
	    }
	  }
	}
      }
      popup.hidePopup();
      popup.setShape(overShape, ppos);
      popup.setPopupLocation(p);
      popup.setComponents(nodeActions[ppos], vertical);	    
      return ppos;
    }
    return -1; 
  }

  protected int makeUMLAssociationPopup(UMLAssociation assoc, 
					GraphEdgeBase edge, 
					boolean end1, boolean end2,
					int ppos, Point p) { 
    if (assoc != null && edge != null) { 
      UMLAssociation.AssociationType assocType = assoc.getType();
      if (assocType == UMLAssociation.AssociationType.Aggregation || 
	  assocType == UMLAssociation.AssociationType.Composition ||
	  assocType == UMLAssociation.AssociationType.Association) { 
	//System.out.println("UMLPointTool assoc edge");
	
	if (assocModelPane == null) { 
	  assocModelPane = new UMLAssociationModelPane(main, true);	       
	}
	assocModelPane.setDrawShape(edge, main);
	
	JComponent endPanel = null; 
	if (end1) {
	  endPanel = assocModelPane.getLeftTitlePanel();
	} else if (end2) {
	  endPanel = assocModelPane.getRightTitlePanel();
	}
	
	if (endPanel != null) { 
	  //popupVisible = false;
	  popup.hidePopup();
	  popup.setShape(edge, ppos);
	  popup.setPopupLocation(p);
	  popup.setComponent(endPanel);
	  return ppos;
	}
      }
      return ppos; 
    }
    return -1; 
  }

  protected int makePolyShapePopup(PolyShape polyshape, int pos, Point p) {
    if (polyshape != null) { 
      int pos1 = pos & HIGH_MASK;
      int pos2 = pos & LOW_MASK;

      int np = polyshape.getNumberOfPoints();
      boolean onVertex = (pos1 == PointOnPolyShapeVertex);
      boolean onEdge = (pos1 == PointOnPolyShapeEdge);	
      int ppos = -1;
      if (onVertex && pos2 > 0 || onEdge) { 
	ppos = CURRENT  + (onVertex ? 1000 : 0) + pos2; 
      }
      if (ppos != -1) { 
	if (popup.isSame(polyshape, ppos)) { 
	  //if (!popup.isShown()) { 
	  popup.setPopupLocation(p);
	  return ppos;
	  //}
	} else { 
	  if (onVertex) {
	    if (debug) { 
	      System.out.println("UMLPointTool.HoverPopupManager: PolyShape vertex " + pos2);
	    }
	    
	    if (pos2 > 0 && np > 2) {
	      // not the anchor vertex and more than 2 vertices 
	      polyShapeAction.polyshape = polyshape;
	      polyShapeAction.position = pos; 
	      //popupVisible = false;
	      popup.hidePopup();
	      popup.setShape(polyshape, ppos);
	      popup.setPopupLocation(p);
	      popup.setComponent(polyShapeButton[0]);
	      return ppos;	      
	    }		    
      	
	  } else if (onEdge) { 
	    if (debug) { // (PointTool.debug) { 
	      System.out.println("UMLPointTool.HoverPopupManager: PolyShape edge");		
	    }
	    
	    polyShapeAction.polyshape = polyshape;
	    polyShapeAction.position = pos; 
	    //popupVisible = false;
	    popup.hidePopup();
	    popup.setShape(polyshape, ppos);
	    popup.setPopupLocation(p);
	    popup.setComponent(polyShapeButton[1]);
	    return ppos;	      	    
	  }
	}
      }
      
    }
    return -1; 
  }

  protected void makeGapPopup(Point p) {
    // switch gap popup when template is changed 

    if (gapPopupEnabled) { 
      if (gapPopup == null) { 
	gapPopup = new GapPopup();
	gapPopup.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200, 128)));
      } else { 
	gapPopup.removeAll();
      }

      if (gapActions != null && 
	  gapActions.length >= 1) { 
	if (gapActions.length == 1) {
	  //int layoutAxis = vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
	  int layoutAxis = BoxLayout.X_AXIS;
	  gapPopup.setLayout(new BoxLayout(gapPopup, layoutAxis));
	  for (int j = 0; j < gapActions[0].length; j++) { 
	    gapPopup.add(gapActions[0][j]);
	  }
	} else {
	  gapPopup.setLayout(new BoxLayout(gapPopup, BoxLayout.Y_AXIS));
	  
	  for (int i = 0; i < gapActions.length; i++) { 
	    JPanel row = new JPanel();
	    row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
	    for (int j = 0; j < gapActions[i].length; j++) { 
	      row.add(gapActions[i][j]);
	    }
	    row.setAlignmentX(Component.LEFT_ALIGNMENT);
	    gapPopup.add(row);
	  }
	}
	gapPopup.pack();
	gapPopup.setPopupLocation(p); 
      }
    }
  }    

  static boolean nearBoundary(DrawShape node, Point2D p) { 
    if (node != null && p != null) { 
      Point2D p1 = node.getEnd1();
      Point2D p2 = node.getEnd2();
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());
      float x  = (float) p.getX();
      float y  = (float) p.getY();
      if (x >= x1 && x <= x2 &&
	  y >= y1 && y <= y2) { 
	return ((x - x1) <= 10 ||
		(x2 - x) <= 10 ||
		(y - y1) <= 10 ||
		(y2 - y) <= 10);
      }
    }
    return false; 
  }

  static int getClosestSide(DrawShape node, Point2D p) { 
    if (node != null && p != null) { 
      Point2D p1 = node.getEnd1();
      Point2D p2 = node.getEnd2();
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());
      float x  = (float) p.getX();
      float y  = (float) p.getY();
      float dx1 = Math.abs(x - x1);
      float dx2 = Math.abs(x - x2);
      float dy1 = Math.abs(y - y1);
      float dy2 = Math.abs(y - y2);
      if (dx1 <= dx2) { 
	if (dy1 <= dy2) { 
	  if (dy1 <= dx1) {
	    return TOP_SIDE;
	  } 
	} else { 
	  if (dy2 <= dx1) {
	    return BOTTOM_SIDE;
	  }
	}
	return LEFT_SIDE; 
      } else { 
	if (dy1 <= dy2) { 
	  if (dy1 <= dx2) {
	    return TOP_SIDE;
	  } 
	} else { 
	  if (dy2 <= dx2) {
	    return BOTTOM_SIDE;
	  }
	}
	return RIGHT_SIDE; 
      }
    }
    return -1; 
  }


  ///////////////////////////

  public boolean isPopupActive() { 
    return popup != null && popup.isActive(); 
  }

  public boolean isPopupVisible() { 
    return popup != null && popup.isShown();
  }

  public boolean insidePopup(Point p) { 
    if (popup != null && 
        popup.isInside(p, 3) && 
        popup.isActive()) { 
      popup.brightenPopup();
      hideGapPopup();
      return true;
    }
    return false; 
  }

  public boolean insideGapPopup(Point p) { 
    if (gapPopup != null && 
        gapPopup.isInside(p, 3) && 
        gapPopup.isActive()) { 
      gapPopup.brightenPopup();
      return true;
    }
    return false; 
  }

  public boolean isInsidePopup(Point p) { 
    return isInsidePopup(p, 0);
  }

  public boolean isInsidePopup(Point p, int d) { 
    if (p != null && 
        popup != null && 
        popup.isShown()) { 
      return popup.isInside(p, d);
    }
    return false; 
  }

  public void brightenPopup() { 
    if (popup != null && 
        popup.isShown()) { 
      popup.brightenPopup();
      hideGapPopup();
    }
  }

  public void showPopup() { 
    if (popup != null && 
        !popup.isShown()) { 
      popup.showPopup();
      hideGapPopup();
    }
  }

  public void hidePopup() { 
    hidePopup(false);
  }

  public void hidePopup(boolean invalidate) { 
    if (popup != null) { 
      if (invalidate) { 
        popup.setShape(null, -1);
      }
      popup.hidePopup();
    }
    hideGapPopup();
  }

  public boolean isGapPopupActive() { 
    return gapPopup != null && gapPopup.isActive(); 
  }

  public boolean isGapPopupVisible() { 
    return gapPopup != null && gapPopup.isShown();
  }

  public void showGapPopup() { 
    if (gapPopupEnabled && 
        gapPopup != null) { 
      gapPopup.showPopup();
    }
  }

  public void hideGapPopup() { 
    //System.out.println("HoverPopupManager.hideGapPopup()");
    if (gapPopup != null) { 
      gapPopup.hidePopup();
    }
  }

  protected HoverPopup popup = new HoverPopup();
  protected GapPopup gapPopup = null;
  protected boolean gapPopupEnabled = true;

  protected static final boolean debug = false; 

  // for hover popup 
  class PolyShapeAction implements ActionListener {
    PolyShape polyshape;
    int position;

    public void actionPerformed(ActionEvent e) {
      Object source = e.getSource();
      if (source instanceof JButton) {
	JButton button = (JButton) source;
	if (isPopupActive()) { 
	  umltool.addRemovePolyShapeVertex(polyshape, position);
	}
	hidePopup();

	/*
	String cmd = button.getActionCommand();
	if ("add".equals(cmd)) {
	    
	} else if ("remove".equals(cmd)) {
	    
	}
	*/
      }
    }
  }

  // for hover popup 
  class CompartmentAction implements ActionListener {
    HorizontalCompartments hc;
    int position;    

    public void actionPerformed(ActionEvent e) {
      Object source = e.getSource();
      if (source instanceof JButton) {
	JButton button = (JButton) source;
	hidePopup();
	String cmd = button.getActionCommand();	
	umltool.addRemoveCompartment(hc, position, "add".equals(cmd));	
      }
    }
  }

  class NodeInsertionAction implements ActionListener {
    
    public NodeInsertionAction() { 
    }
    
    public void actionPerformed(ActionEvent e) {
      if (e != null) {
	if (isGapPopupActive()) { 
	  Object src = e.getSource();
	  if (src != null && 
	      src instanceof AbstractButton) {
	    AbstractButton b = (AbstractButton) src;
	    String cmd = b.getActionCommand();
	    view.setCurrentTool(cmd);
	    main.displayHelpMessage(cmd + ".message");
	    
	    SimpleDrawTool tool = (SimpleDrawTool) view.getCurrentTool();
	    tool.start(view);
	    //tool.mouseLButtonPressed(view, mousePosition, 0);
	    tool.mouseMoved(view, umltool.mousePosition, 0);
	  }
	  hideGapPopup();
	}
      }
    }
    
  }

  class SubnodeInsertionAction implements ActionListener {
    
    public SubnodeInsertionAction() { 
    }

    public void setParent(GraphNodeBase parent) { 
      SubnodeInsertionAction.this.parent = parent; 
    }

    public void actionPerformed(ActionEvent e) {
      if (e != null) {
	Object src = e.getSource();
	if (src != null && 
	    src instanceof AbstractButton) {
	  AbstractButton b = (AbstractButton) src;
	  String cmd = b.getActionCommand();
	  view.setCurrentTool(cmd);
	  //main.displayHelpMessage(cmd + ".message");
	  hidePopup();

	  if (parent != null) { 
	    SimpleDrawTool tool = (SimpleDrawTool) view.getCurrentTool();
	    tool.start(view);
	    tool.mouseLButtonPressed(view, umltool.mousePosition, 0);
	    //tool.mouseMoved(view, umltool.mousePosition, 0);
	  }
	}

      }
    }

    GraphNodeBase parent; 
    
  }

  class UMLClassPopupAction implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      Object source = e.getSource();
      if (source instanceof AbstractButton) {
	AbstractButton button = (AbstractButton) source;
	hidePopup(true);
	if (shape != null) { 
	  String cmd = button.getActionCommand();
	  umltool.hideShowClassCompartments(shape, "show".equals(cmd));
	}
      }
    }
    ClassShape shape = null;
  }


  class NodeEdgeAction 
    implements ActionListener {
    
    NodeEdgeAction() { 
    }
  
    public void setNode(GraphNodeBase node) { 
      NodeEdgeAction.this.node = node; 
    }
    
    public void setSide(int side) { 
      this.side = side; 
    }
    
    public void actionPerformed(ActionEvent e) {
      if (e != null) {
	Object src = e.getSource();
	if (src != null && 
	    src instanceof AbstractButton) {
	  AbstractButton b = (AbstractButton) src;
	  String cmd = b.getActionCommand();
	  view.setCurrentTool(cmd);
	  hidePopup();
	  //main.displayHelpMessage(cmd + ".message");
	  
	  if (node != null) { 
	    DrawTool tool = view.getCurrentTool();
	    if (tool instanceof NodeEdgeTool) { 
	      NodeEdgeTool etool = (NodeEdgeTool) tool;
	      
	      Point2D p1 = node.getEnd1();
	      Point2D p2 = node.getEnd2();
	      float x1 = (float) Math.min(p1.getX(), p2.getX());
	      float y1 = (float) Math.min(p1.getY(), p2.getY());
	      float w  = (float) Math.abs(p1.getX() - p2.getX()) / 2;
	      float h  = (float) Math.abs(p1.getY() - p2.getY()) / 2;
	      float x = x1 + w;
	      float y = y1 + h;
	      float d = 5; 
	      if (d > h) d = h;
	      if (d > w ) d = w;
	      switch(side) { 
	      case TOP_SIDE:
		y -= (h  - d); break; 
	      case BOTTOM_SIDE:
		y += (h  - d); break; 
	      case LEFT_SIDE:
		x -= (w - d); break; 
	      case RIGHT_SIDE:
		x += (w - d); break; 
	      }
	      Point2D cv = new Point2D.Float(x, y); 
	      Point cp = view.virtualToPhysical(cv); 
	      etool.startTool(view, cp, 0);
	      DrawCanvas canvas = view.getCanvas();
	      canvas.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	      Point p = canvas.getMousePosition();	      
	      //System.out.println("NodeEdgeAction.actionPerformed(): p == null: " + (p == null));
	      if (p == null) { 
		p = new Point(cp.x, cp.y);
	      }
	      etool.mouseMoved(view, p, MouseEvent.BUTTON1);
	    }
	  }
	}
      }
    }
  
    GraphNodeBase node; 
    int side; // TOP_SIDE BOTTOM_SIDE LEFT_SIDE RIGHT_SIDE 
  }

  // for hover popup 
  class PopupMouseAction extends MouseAdapter { 
    @Override
    public void mouseEntered(MouseEvent e) {
      brightenPopup();
    }
  }


  /// FadeInPopup animation 

  class AlphaAnimation implements Runnable {
  
    AlphaAnimation(FadeInPopup comp, int[] steps) { 
      this.comp = comp;
      this.steps = steps; 
      comp.setAlpha(0f);
    }
  
    AlphaAnimation(FadeInPopup comp) { 
      this.comp = comp;
      comp.setAlpha(0f);
    }

    public void run() { 
      while (true) { 
	try { 
	  if (stage == 0) {
	    if (step >= steps[START] && step <= steps[BRIGHT]) { 
	      if (step >= steps[SHOW]) {
		if (step == steps[SHOW]) { 
		  comp.makeVisible();
		}
		float r = ((float) (step - steps[SHOW])) / ((float) (steps[BRIGHT] - steps[SHOW]));
		float a = alpha[0] * r;
		comp.setAlpha(a);
		comp.setActive(true);
	      }
	    } 
	    step++; 	    
	    if (step >= (steps[BRIGHT] + 50)) { 
	      stage = 2; 
	      step = steps[BRIGHT];
	    }
	    Thread.sleep(DELAY);
	  } else if (stage == 1) { 
	    if (step >= steps[BRIGHT] && step <= steps[LAST]) { 
	      float r = ((float) (step - steps[BRIGHT])) / ((float) (steps[LAST] - steps[BRIGHT]));
	      float a = alpha[0] + (alpha[1] - alpha[0]) * r;
	      comp.setAlpha(a);
	    } 
	    step++; 
	    Thread.sleep(DELAY);
	  } else if (stage == 2) { 
	    if (step >= steps[SHOW] && step <= steps[BRIGHT]) {
	      if (step == steps[SHOW]) { 
		step = -1; 
		stage = -1; 
		comp.setAlpha(0f);
		comp.setActive(false);
		comp.setShown(false);
		comp.setActive(false);
	      } else {
		float r = ((float) (step - steps[SHOW])) / ((float) (steps[BRIGHT] - steps[SHOW]));
		float a = alpha[0] * r;
		comp.setAlpha(a);
	      }
	    }
	    step--;
	    Thread.sleep(DELAY);
	  } else { 
	    synchronized(AlphaAnimation.this) {
	      AlphaAnimation.this.wait(); 
	    }
	  }
	} catch (InterruptedException e) { 
	  break;
	}
      }
    }
  
    int step = -1;
    int stage = -1; 
    FadeInPopup comp; 
  
    public synchronized void show() {
      step = 0; 
      stage = 0; 
      comp.setAlpha(0f);
      comp.setShown(true);
      comp.setActive(false);
      AlphaAnimation.this.notify();      
    }
  
    public synchronized void brighten() {
      step = Math.max(step, steps[BRIGHT]); 
      stage = 1;       
      comp.setActive(true);
      AlphaAnimation.this.notify();
    }

    public synchronized void stop() { 
      step = -1; 
      stage = -1; 
      comp.setAlpha(0f);
      comp.setActive(false);
      comp.setShown(false);
    }
  
    int[] steps = { 0, 20, 30, 40 };
    //{ 0, 10, 20, 30 };
    float[] alpha = { 0.3f, 0.7f }; 
  
    static final int START  = 0;
    static final int SHOW   = 1;
    static final int BRIGHT = 2; 
    static final int LAST   = 3; 
  
    static final int DELAY = 100; 
  }

  class FadeInPopup extends JPanelPopup { 
  
    FadeInPopup() {
      this(null);
    }
  
    FadeInPopup(int[] steps) {
      super(false);
      if (steps == null) { 
	animation = new AlphaAnimation(this);
      } else { 
	animation = new AlphaAnimation(this, steps);
      }
      new Thread(animation).start(); 
    }
  
    public boolean isActive() { 
      return active; 
    }
  
    public void setActive(boolean b) { 
      active = b; 
    }
  
    public boolean isShown() { 
      return shown;
    }
  
    public void setShown(boolean b) { 
      shown = b;
    }
  
    void setPopupLocation(Point p) { 
      if (p != null) { 
	px = p.x;
	py = p.y;
      }
    }
  
    void setComponent(JComponent comp) {
      if (comp != null) { 
	removeAll();
	setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200, 128)));
	add(comp, BorderLayout.CENTER);
	pack();
      }
    }

    void setComponents(JComponent[] comp, boolean vertical) {
      //System.out.println("HOverPopupManager.FadeInPopup.setComponents() " + comp.length);
      if (comp != null && 
	  comp.length > 0) { 
	removeAll();
	setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200, 128)));
	int layoutAxis = vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
	setLayout(new BoxLayout(this, layoutAxis));
	for (int i = 0; i < comp.length; i++) { 
	  add(comp[i]);
	}
	pack();
      }
    }
    
    public void showPopup() { 
      shown = true; 
      setAlpha(0f);
      animation.show();
    }

    public void makeVisible() { 
      view.showPopup(this, px, py);
    }

    public void brightenPopup() { 
      active = true;
      makeVisible();
      animation.brighten();
    }

    @Override
	public void hidePopup() { 
      shown = false;
      active = false;
      super.hidePopup();
      animation.stop();
    }

    public boolean isInside(Point p, int d) { 
      if (p != null) { 
	Rectangle r = getBounds();
	Point ps = SwingUtilities.convertPoint(view.getCanvas(), 0, 0, getParent());
	int x1 = r.x - ps.x;
	int y1 = r.y - ps.y;
	int x2 = x1 + r.width; 
	int y2 = y1 + r.height; 

	if (debug) { 
	  System.out.println("FadePopup popup.bounds: " + r + "  ps: " + ps + 
			     "  [" + x1 + "," + y1 + "," + x2 + "," + y2 + "]"); 
	}

	if (p.x >= x1 + d &&  p.x < x2 - d && 
	    p.y >= y1 + d &&  p.y < y2 - d)  { 
	  return true;
	}
      }
      return false; 
    }   

    AlphaAnimation animation = null; 
    
    boolean shown = false;
    boolean active = false;

    int px, py; // the location of the popup in the device space of the view        
  }

  class GapPopup extends FadeInPopup { 

    GapPopup() {
      super(new int[] { 0, 20, 30, 40 });
    }

  }

  class HoverPopup extends FadeInPopup { 

    HoverPopup() {
    }

    public boolean isSame(DrawShape shape, int position) { 
      return (this.shape == shape && 
	      this.position == position);
    }

    public void setShape(DrawShape shape, int position) { 
      this.shape = shape; 
      this.position = position;
    }
  
    @Override
    public void showPopup() { 
      if (position != -1) { 
	Dimension dim = HoverPopup.this.getSize();
	//System.out.println("HoverPopup dim=" + dim); 
	int dw = dim.width; 
	int dh = dim.height;
	if (position >= HoverPopupManager.CURRENT) { 
	  setAlpha(0f);
	  shown = true; 
	  animation.show();
	} else if (position >= HoverPopupManager.INTERIOR) { 
	  setAlpha(0f);
	  shown = true; 
	  animation.show();
	} else if (position == HoverPopupManager.TOP_SIDE || 
		   position == HoverPopupManager.BOTTOM_SIDE || 
		   position == HoverPopupManager.LEFT_SIDE || 
		   position == HoverPopupManager.RIGHT_SIDE) { 
	  /*
	  Rectangle2D b = shape.getBounds();
	  float x = (float) b.getX();
	  float y = (float) b.getY();
	  float w = (float) b.getWidth();
	  float h = (float) b.getHeight();
	  */
	  Point2D p1= shape.getEnd1();
	  Point2D p2= shape.getEnd2();
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float w = (float) Math.abs(p2.getX() - p1.getX());
	  float h = (float) Math.abs(p2.getY() - p1.getY());
	  if (position == HoverPopupManager.BOTTOM_SIDE || 
	      position == HoverPopupManager.TOP_SIDE) { 
	    x += w / 2;
	  }
	  if (position == HoverPopupManager.LEFT_SIDE || 
	      position == HoverPopupManager.RIGHT_SIDE) { 
	    y += h / 2;
	  }
	  if (position == HoverPopupManager.BOTTOM_SIDE) { 
	    if (shape instanceof RectangularShape) { 
	      RectangularShape rshape = (RectangularShape) shape;
	      int rtype = rshape.getShapeType();
	      if (rtype == ShapeConstants.ACTOR_SHAPE || 
		  rtype == ShapeConstants.TERMINAL_SHAPE) { 
		h = rshape.getShapeHeight();
	      }
	    }
	    y += h;
	  } else if (position == HoverPopupManager.TOP_SIDE) { 
	    if (shape instanceof TitledShape) { 
	      TitledShape tshape = (TitledShape) shape;
	      float h0 = tshape.getTitleShape().getShapeHeight();
	      y += h0;
	    }
	  } else if (position == HoverPopupManager.RIGHT_SIDE) { 
	    x += w;
	  }
	  Point p3 = view.virtualToPhysical(new Point2D.Float(x, y));
	  px = p3.x;
	  py = p3.y;
	  if (position == HoverPopupManager.LEFT_SIDE) { 
	    px = px - dw + 6; 
	    py = py - dh / 2; 
	  } else if (position == HoverPopupManager.RIGHT_SIDE) {
	    //px = px - 5; 
	    py = py - dh / 2; 
	  } else if (position == HoverPopupManager.TOP_SIDE) {
	    px = px - dw / 2;
	    py = py - dh + 6; 
	  } else if (position == HoverPopupManager.BOTTOM_SIDE) {
	    px = px - dw / 2;
	    //py = py - 5; 
	  } 	  
	  setAlpha(0f);
	  shown = true; 
	  animation.show();
	} 
      }
    }

    @Override
    public boolean isInside(Point p, int d) { 
      if (p != null) { 
	Rectangle r = getBounds();
	Point ps = SwingUtilities.convertPoint(view.getCanvas(), 0, 0, getParent());
	int x1 = r.x - ps.x;
	int y1 = r.y - ps.y;
	int x2 = x1 + r.width; 
	int y2 = y1 + r.height; 

	if (debug) { 
	  System.out.println("HoverPopup popup.bounds: " + r + "  ps: " + ps + 
			     "  [" + x1 + "," + y1 + "," + x2 + "," + y2 + "]"); 
	}

	if (position == HoverPopupManager.TOP_SIDE) { 
	  y1 -= 10;
	} else if (position == HoverPopupManager.BOTTOM_SIDE) { 
	  y2 += 10;
	} else if (position == HoverPopupManager.LEFT_SIDE) { 
	  x1 -= 10;
	} else if (position == HoverPopupManager.RIGHT_SIDE) { 
	  x2 += 10;
	}

	if (p.x >= x1 + d &&  p.x < x2 - d && 
	    p.y >= y1 + d &&  p.y < y2 - d)  { 
	  return true;
	}
      }
      return false; 
    }   

    DrawShape shape;   // the owner of the popup  
    int position = -1; // the position of the popup in relation to the owner

  }
  

}



////////////////////////////////////
// tool linked to hover popup button
//////////////////////////////////// 

class NodeEdgeTool extends SimpleDrawTool {

  NodeEdgeTool(SimpleDrawTool tool) { 
    this.tool = tool;
  }

  public void startTool(Observer observer, Point p, int modifiers) { 
    //System.out.println("NodeEdgeTool.startTool() reverseEnds: " + 
    //((TwoEndsDrawTool) tool).isReverseEnds());
    if (tool != null) { 
      tool.mouseLButtonPressed(observer, p, modifiers);
    }
  }

  @Override
  public void mouseMoved(Observer observer, Point p, int modifiers) { 
    //System.out.println("NodeEdgeTool.mouseMoved()");
    if (tool != null) { 
      tool.mouseDragged(observer, p, modifiers);
    }
  }

  @Override
  public void mouseDragged(Observer observer, Point p, int modifiers) { 
    //System.out.println("NodeEdgeTool.mouseDraged()");
    if (tool != null) { 
      tool.mouseDragged(observer, p, modifiers);
    }
  }

  @Override
  public void mouseLButtonReleased(Observer observer, Point p, int modifiers) {
    //System.out.println("NodeEdgeTool.mouseLButtonReleased()");
    if (tool != null) { 
      tool.mouseLButtonReleased(observer, p, modifiers);
    }
  }

  @Override
  public void popupTriggered(Observer observer, Point p, int modifiers) {
    if (tool != null) { 
      canvas.clearRepaintShapes();
      canvas.repaint();

      tool.switchTool(); 
    }
  }

  SimpleDrawTool tool;

}

  
